package com.lwlk.util.tuple;

import java.io.Serializable;
import java.util.*;

import java.util.stream.StreamSupport;

public final class Tuple1<T1> 
    implements Iterable<Object>,
               Serializable,
               Comparable<Tuple1> {
    
    private static final int size = 1;
    
    private final T1 value1;
    
    public Tuple1(final T1 arg1) {
        this.value1 = arg1;
    }
    
    public static <T1> Tuple1<T1> with(final T1 arg1) {
        return new Tuple1<>(arg1);
    }

    public static <X> Tuple1<X> fromArray(final X[] array) {
        if (array == null) {
            throw new IllegalArgumentException("Array cannot be null");
        }
        if (array.length != 1) {
            throw new IllegalArgumentException("Array must have exactly 1 elements in order to create this Tuple. Size is " + array.length);
        }      
        return new Tuple1<>(array[0]);
    }
    
    public static <X> Tuple1<X> fromCollection(final Collection<X> coll) {
        return fromIterable(coll);
    }
    
    public static <X> Tuple1<X> fromIterable(final Iterable<X> iterable) {
        return fromIterable(iterable, 0);
    }
    
    @SuppressWarnings("unchecked")
    public static <X> Tuple1<X> fromIterable(final Iterable<X> iterable, final int index) {
        if (iterable == null) {
            throw new IllegalArgumentException("Iterable cannot be null");
        }
        return fromArray((X[]) StreamSupport.stream(iterable.spliterator(), false).skip(index).toArray());
    }
    
    public <X> Tuple1<X> setAt1(final X arg) { return new Tuple1<>(arg); }

    public <X1> Tuple2<X1, T1> insertArgsAt0(final X1 arg1) { return new Tuple2<>(arg1, value1); }
	public <X1> Tuple2<T1, X1> insertArgsAt1(final X1 arg1) { return new Tuple2<>(value1, arg1); }
	public <X1, X2> Tuple3<X1, X2, T1> insertArgsAt0(final X1 arg1, final X2 arg2) { return new Tuple3<>(arg1, arg2, value1); }
	public <X1, X2> Tuple3<T1, X1, X2> insertArgsAt1(final X1 arg1, final X2 arg2) { return new Tuple3<>(value1, arg1, arg2); }
	public <X1, X2, X3> Tuple4<X1, X2, X3, T1> insertArgsAt0(final X1 arg1, final X2 arg2, final X3 arg3) { return new Tuple4<>(arg1, arg2, arg3, value1); }
	public <X1, X2, X3> Tuple4<T1, X1, X2, X3> insertArgsAt1(final X1 arg1, final X2 arg2, final X3 arg3) { return new Tuple4<>(value1, arg1, arg2, arg3); }
	public <X1, X2, X3, X4> Tuple5<X1, X2, X3, X4, T1> insertArgsAt0(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4) { return new Tuple5<>(arg1, arg2, arg3, arg4, value1); }
	public <X1, X2, X3, X4> Tuple5<T1, X1, X2, X3, X4> insertArgsAt1(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4) { return new Tuple5<>(value1, arg1, arg2, arg3, arg4); }
	public <X1, X2, X3, X4, X5> Tuple6<X1, X2, X3, X4, X5, T1> insertArgsAt0(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5) { return new Tuple6<>(arg1, arg2, arg3, arg4, arg5, value1); }
	public <X1, X2, X3, X4, X5> Tuple6<T1, X1, X2, X3, X4, X5> insertArgsAt1(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5) { return new Tuple6<>(value1, arg1, arg2, arg3, arg4, arg5); }
	public <X1, X2, X3, X4, X5, X6> Tuple7<X1, X2, X3, X4, X5, X6, T1> insertArgsAt0(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5, final X6 arg6) { return new Tuple7<>(arg1, arg2, arg3, arg4, arg5, arg6, value1); }
	public <X1, X2, X3, X4, X5, X6> Tuple7<T1, X1, X2, X3, X4, X5, X6> insertArgsAt1(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5, final X6 arg6) { return new Tuple7<>(value1, arg1, arg2, arg3, arg4, arg5, arg6); }
	public <X1, X2, X3, X4, X5, X6, X7> Tuple8<X1, X2, X3, X4, X5, X6, X7, T1> insertArgsAt0(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5, final X6 arg6, final X7 arg7) { return new Tuple8<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, value1); }
	public <X1, X2, X3, X4, X5, X6, X7> Tuple8<T1, X1, X2, X3, X4, X5, X6, X7> insertArgsAt1(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5, final X6 arg6, final X7 arg7) { return new Tuple8<>(value1, arg1, arg2, arg3, arg4, arg5, arg6, arg7); }
	public <X1, X2, X3, X4, X5, X6, X7, X8> Tuple9<X1, X2, X3, X4, X5, X6, X7, X8, T1> insertArgsAt0(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5, final X6 arg6, final X7 arg7, final X8 arg8) { return new Tuple9<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, value1); }
	public <X1, X2, X3, X4, X5, X6, X7, X8> Tuple9<T1, X1, X2, X3, X4, X5, X6, X7, X8> insertArgsAt1(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5, final X6 arg6, final X7 arg7, final X8 arg8) { return new Tuple9<>(value1, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); }
	public <X1, X2, X3, X4, X5, X6, X7, X8, X9> Tuple10<X1, X2, X3, X4, X5, X6, X7, X8, X9, T1> insertArgsAt0(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5, final X6 arg6, final X7 arg7, final X8 arg8, final X9 arg9) { return new Tuple10<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, value1); }
	public <X1, X2, X3, X4, X5, X6, X7, X8, X9> Tuple10<T1, X1, X2, X3, X4, X5, X6, X7, X8, X9> insertArgsAt1(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4, final X5 arg5, final X6 arg6, final X7 arg7, final X8 arg8, final X9 arg9) { return new Tuple10<>(value1, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); }
    
    public <X1> Tuple2<X1, T1> insertTupleAt0(final Tuple1<X1> tuple) { return new Tuple2<>(tuple.getValue1(), value1); }
	public <X1> Tuple2<T1, X1> insertTupleAt1(final Tuple1<X1> tuple) { return new Tuple2<>(value1, tuple.getValue1()); }
	public <X1, X2> Tuple3<X1, X2, T1> insertTupleAt0(final Tuple2<X1, X2> tuple) { return new Tuple3<>(tuple.getValue1(), tuple.getValue2(), value1); }
	public <X1, X2> Tuple3<T1, X1, X2> insertTupleAt1(final Tuple2<X1, X2> tuple) { return new Tuple3<>(value1, tuple.getValue1(), tuple.getValue2()); }
	public <X1, X2, X3> Tuple4<X1, X2, X3, T1> insertTupleAt0(final Tuple3<X1, X2, X3> tuple) { return new Tuple4<>(tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), value1); }
	public <X1, X2, X3> Tuple4<T1, X1, X2, X3> insertTupleAt1(final Tuple3<X1, X2, X3> tuple) { return new Tuple4<>(value1, tuple.getValue1(), tuple.getValue2(), tuple.getValue3()); }
	public <X1, X2, X3, X4> Tuple5<X1, X2, X3, X4, T1> insertTupleAt0(final Tuple4<X1, X2, X3, X4> tuple) { return new Tuple5<>(tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), value1); }
	public <X1, X2, X3, X4> Tuple5<T1, X1, X2, X3, X4> insertTupleAt1(final Tuple4<X1, X2, X3, X4> tuple) { return new Tuple5<>(value1, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4()); }
	public <X1, X2, X3, X4, X5> Tuple6<X1, X2, X3, X4, X5, T1> insertTupleAt0(final Tuple5<X1, X2, X3, X4, X5> tuple) { return new Tuple6<>(tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5(), value1); }
	public <X1, X2, X3, X4, X5> Tuple6<T1, X1, X2, X3, X4, X5> insertTupleAt1(final Tuple5<X1, X2, X3, X4, X5> tuple) { return new Tuple6<>(value1, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5()); }
	public <X1, X2, X3, X4, X5, X6> Tuple7<X1, X2, X3, X4, X5, X6, T1> insertTupleAt0(final Tuple6<X1, X2, X3, X4, X5, X6> tuple) { return new Tuple7<>(tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5(), tuple.getValue6(), value1); }
	public <X1, X2, X3, X4, X5, X6> Tuple7<T1, X1, X2, X3, X4, X5, X6> insertTupleAt1(final Tuple6<X1, X2, X3, X4, X5, X6> tuple) { return new Tuple7<>(value1, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5(), tuple.getValue6()); }
	public <X1, X2, X3, X4, X5, X6, X7> Tuple8<X1, X2, X3, X4, X5, X6, X7, T1> insertTupleAt0(final Tuple7<X1, X2, X3, X4, X5, X6, X7> tuple) { return new Tuple8<>(tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5(), tuple.getValue6(), tuple.getValue7(), value1); }
	public <X1, X2, X3, X4, X5, X6, X7> Tuple8<T1, X1, X2, X3, X4, X5, X6, X7> insertTupleAt1(final Tuple7<X1, X2, X3, X4, X5, X6, X7> tuple) { return new Tuple8<>(value1, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5(), tuple.getValue6(), tuple.getValue7()); }
	public <X1, X2, X3, X4, X5, X6, X7, X8> Tuple9<X1, X2, X3, X4, X5, X6, X7, X8, T1> insertTupleAt0(final Tuple8<X1, X2, X3, X4, X5, X6, X7, X8> tuple) { return new Tuple9<>(tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5(), tuple.getValue6(), tuple.getValue7(), tuple.getValue8(), value1); }
	public <X1, X2, X3, X4, X5, X6, X7, X8> Tuple9<T1, X1, X2, X3, X4, X5, X6, X7, X8> insertTupleAt1(final Tuple8<X1, X2, X3, X4, X5, X6, X7, X8> tuple) { return new Tuple9<>(value1, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5(), tuple.getValue6(), tuple.getValue7(), tuple.getValue8()); }
	public <X1, X2, X3, X4, X5, X6, X7, X8, X9> Tuple10<X1, X2, X3, X4, X5, X6, X7, X8, X9, T1> insertTupleAt0(final Tuple9<X1, X2, X3, X4, X5, X6, X7, X8, X9> tuple) { return new Tuple10<>(tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5(), tuple.getValue6(), tuple.getValue7(), tuple.getValue8(), tuple.getValue9(), value1); }
	public <X1, X2, X3, X4, X5, X6, X7, X8, X9> Tuple10<T1, X1, X2, X3, X4, X5, X6, X7, X8, X9> insertTupleAt1(final Tuple9<X1, X2, X3, X4, X5, X6, X7, X8, X9> tuple) { return new Tuple10<>(value1, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), tuple.getValue5(), tuple.getValue6(), tuple.getValue7(), tuple.getValue8(), tuple.getValue9()); }
    
    public Object getValue(final int pos) { 
        if(pos > getSize() || pos <= 0) {
            throw new IllegalArgumentException(
                    "Cannot retrieve position " + pos + " in " + this.getClass().getSimpleName() + 
                    ". Positions for this class start with 1 and end with " + getSize());
        }      
        switch(pos) {
            case 1: return value1;
        }
        return null; // just make java happy!
    }
    
    public List<Object> toList() {
        return Collections.unmodifiableList(Arrays.asList(value1));
    }
    
    public Object[] toArray() {
        return new Object[] { value1 };
    }

    @Override
    public Iterator<Object> iterator() { return toList().iterator(); }

    @Override
    public int hashCode() {
        return Objects.hash(value1);
    }
    
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Tuple1 tmp = (Tuple1) obj;
        return this.compareTo(tmp) == 0;
    }    
    
    @Override
    public int compareTo(final Tuple1 t) {
        for(int i = 1; i <= getSize(); ++i) {
            final int comparison = ((Comparable) getValue(i)).compareTo(t.getValue(i));
            if(comparison != 0) return comparison;
        }
        return 0;
    }
    
    public int getSize() { return size; }
    
    public T1 getValue1() { return value1; }
}
